import asyncPool from 'tiny-async-pool';
import NProgress from 'nprogress';
import utils from "./utils";

const DEFAULT_CHUNK_SIZE = 1 * 1024 * 1024;

/**
 * 获取文件的长度
 * @param url
 * @returns {Promise<unknown>}
 */
const getContentLength = url => {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('HEAD', url);
        xhr.send();
        xhr.onload = function () {
            resolve({
                size: xhr.getResponseHeader('Content-Length'),
                // chunkSize: xhr.getResponseHeader('Package-Len'),
            });
        };
        xhr.onerror = reject;
    });
};

/**
 * 用于根据传入的参数发起范围请求，从而下载指定范围内的文件数据块
 * @param url
 * @param start
 * @param end
 * @param i
 */
const getBinaryContent = (url, start, end, i) => {
    return new Promise((resolve, reject) => {
        try {
            let xhr = new XMLHttpRequest();
            xhr.open("GET", url, true);
            xhr.setRequestHeader("range", `bytes=${start}-${end}`); // 请求头上设置范围请求信息
            xhr.responseType = "arraybuffer"; // 设置返回的类型为arraybuffer
            xhr.onload = function () {
                resolve({
                    index: i, // 文件块的索引
                    buffer: xhr.response, // 范围请求对应的数据
                });
            };
            xhr.send();
        } catch (err) {
            reject(new Error(err));
        }
    });
};

const concatenate = arrays => {
    if (!utils.isNotEmptyList(arrays)) {
        return null;
    }
    let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
    let result = new Uint8Array(totalLength);
    let length = 0;
    for (let array of arrays) {
        result.set(array, length);
        length += array.length;
    }
    return result;
};

const doDownloadBuffer = async ({url, chunkSize = DEFAULT_CHUNK_SIZE, poolLimit = 1, onProgress}) => {
    const contentInfo = await getContentLength(url);
    if (utils.isNotEmpty(contentInfo.chunkSize)) {
        chunkSize = Number(contentInfo.chunkSize);
    }
    const chunks = Math.ceil(contentInfo.size / chunkSize);
    const results = await asyncPool(poolLimit, [...new Array(chunks).keys()], i => {
        let start = i * chunkSize;
        let end = i + 1 === chunks ? contentInfo.size - 1 : (i + 1) * chunkSize - 1;
        if (typeof onProgress === 'function') {
            console.log(onProgress);
            let ratio = utils.accMul(i, 100);
            ratio = utils.accDiv2Fixed(ratio, chunks, 0);
            ratio = Number(ratio);
            NProgress.set(ratio / 100);
            onProgress({
                start, end, i, chunkSize, size: contentInfo.size,
                ratio,
            });
        }
        return getBinaryContent(url, start, end, i);
    });
    const sortedBuffers = results.map(item => new Uint8Array(item.buffer));
    return concatenate(sortedBuffers);
};

const saveAs = ({name, buffers, mime = "application/octet-stream"}) => {
    const blob = new Blob([buffers], {type: mime});
    if (window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, fileName);
    } else {
        const blobUrl = window.URL.createObjectURL(blob);
        const link = document.createElement("a");
        if (utils.isNotEmpty(name)) {
            link.download = name;
        } else {
            link.download = String(new Date().getTime());
        }
        link.href = blobUrl;
        link.click();
        window.URL.revokeObjectURL(blobUrl);
    }
};

export const download = (options) => {
    NProgress.start();
    doDownloadBuffer(options).then(buffers => {
        NProgress.done();
        saveAs({name: options.name, buffers, mime: options.mime});
    });
};

export const downloadBuffer = (options) => {
    return doDownloadBuffer(options);
};
